package com.example.swimmingpool.service.order;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.swimmingpool.SwimmingPoolThreadLocal;
import com.example.swimmingpool.constants.RedisConstants;
import com.example.swimmingpool.entity.goods.Goods;
import com.example.swimmingpool.entity.order.Order;
import com.example.swimmingpool.entity.order_info.OrderInfo;
import com.example.swimmingpool.mapper.goods.GoodsMapper;
import com.example.swimmingpool.mapper.order.OrderMapper;
import com.example.swimmingpool.mapper.order_info.OrderInfoMapper;
import com.example.swimmingpool.qo.order.OrderQo;
import com.example.swimmingpool.vo.order.OrderBuyVo;
import com.example.swimmingpool.vo.order.OrderInfoVo;
import com.example.swimmingpool.vo.order.OrderTmpInfoVo;
import com.example.swimmingpool.vo.order.OrderVo;
import jakarta.annotation.Resource;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

@Service
public class OrderService {
    @Resource
    private OrderMapper orderMapper;

    @Resource
    private OrderInfoMapper orderInfoMapper;

    @Resource
    private GoodsMapper goodsMapper;

    @Resource
    private Redisson redisson;

    @Resource
    private TransactionTemplate tx;


    public boolean buy(OrderVo vo) {
        RLock lock = redisson.getLock(RedisConstants.ORDER_LOCK_KEY);
        try {
            lock.lock();
            Order order = new Order();
            orderMapper.insert(order);
            var orderId = order.getId();
            AtomicReference<BigDecimal> sumPrice = new AtomicReference<>(new BigDecimal(0));
            var buyFlag = tx.execute(status -> {
                AtomicReference<Integer> success = new AtomicReference<>(1);

                vo.getOrderBuyVos().forEach(it -> {

                    Goods goods = goodsMapper.selectById(it.getGoodsId());
                    var tmpPrice = it.getAccount() * goods.getPrice().doubleValue();
                    sumPrice.set(new BigDecimal(tmpPrice + sumPrice.get().doubleValue()));
                    if (goods.getAccount() - it.getAccount() >= 0) {
                        success.updateAndGet(v -> v & goodsMapper.updateAccount(goods.getId(), it.getAccount()));

                        OrderInfo orderInfo = new OrderInfo();
                        orderInfo.setOrderId(orderId);
                        orderInfo.setGoodsCount(it.getAccount());
                        orderInfo.setGoodsId(goods.getId());
                        orderInfoMapper.insert(orderInfo);

                    } else {
                        success.set(0);
                    }


                });
                if (success.get() == 1) {
                    return Boolean.TRUE;
                }
                status.setRollbackOnly();
                return Boolean.FALSE;


            });
            order.setPrice(sumPrice.get());
            if (Boolean.TRUE.equals(buyFlag)) {
                order.setOrderStatus("success");
                orderMapper.updateById(order);
                return true;

            }
            order.setOrderStatus("fail");
            orderMapper.updateById(order);
            return false;

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return false;


    }

    public List<OrderInfoVo> mapToList(Map<Long, List<OrderTmpInfoVo>> orderMap, String sortKey) {
        return orderMap
                .values()
                .stream()
                .map(v -> {
                    List<OrderBuyVo> orderBuyVos = v.stream().map(it -> new OrderBuyVo(it.getGoodsId(), it.getGoodsName(), it.getGoodsCount())).toList();
                    assert CollectionUtil.isNotEmpty(v);
                    OrderInfoVo orderInfoVo = BeanUtil.copyProperties(v.get(0), OrderInfoVo.class, " orderBuyVos");
                    orderInfoVo.setOrderBuyVos(orderBuyVos);
                    return orderInfoVo;
                })
                .sorted(this.getComparator(sortKey))
                .toList();
    }

    public IPage<OrderInfoVo> list(OrderQo qo) {
        IPage<Long> ids = orderMapper.getBuyListCount(new Page<>(qo.getCurrent(), qo.getSize()), qo);
        List<OrderTmpInfoVo> buyList = orderMapper.getBuyList(qo, ids.getRecords());
        Map<Long, List<OrderTmpInfoVo>> orderMap = buyList.stream().collect(Collectors.groupingBy(OrderTmpInfoVo::getId));
        List<OrderInfoVo> orderInfoVos = this.mapToList(orderMap, Optional.ofNullable(qo.getSort()).orElse("no"));
        AtomicInteger idx = new AtomicInteger(-1);
        return ids.convert((it) -> {
            idx.addAndGet(1);
            return orderInfoVos.get(idx.get());
        });

    }

    public Comparator<OrderInfoVo> getComparator(String sortKey) {
        return switch (sortKey) {
            case "price" -> Comparator.comparing(OrderInfoVo::getPrice);
            case "order_time" -> Comparator.comparing(OrderInfoVo::getOrderTime).reversed();
            default -> Comparator.comparing(OrderInfoVo::getId).reversed();
        };

    }

    public List<OrderInfoVo> listMy() {
        var userId = SwimmingPoolThreadLocal.getUserId();
        OrderQo qo = new OrderQo();
        qo.setUserId(userId);
        List<Long> ids = orderMapper.getBuyListCount(qo);
        List<OrderTmpInfoVo> buyList = orderMapper.getBuyList(qo, ids);
        Map<Long, List<OrderTmpInfoVo>> orderMap = buyList.stream().collect(Collectors.groupingBy(OrderTmpInfoVo::getId));
        return this.mapToList(orderMap,"id");


    }

}
